package com.naiterui.ehp.bs.pharmacist.service.impl;

import com.google.common.collect.Lists;
import com.naiterui.common.redis.RedisUtil;
import com.naiterui.ehp.bp.constants.CommonConstant;
import com.naiterui.ehp.bp.constants.EsignServiceProviderEnum;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.support.exception.PublicService;
import com.naiterui.ehp.bp.support.exception.ResponseVO;
import com.naiterui.ehp.bp.support.security.HeaderConstant;
import com.naiterui.ehp.bp.support.utils.IpUtil;
import com.naiterui.ehp.bp.utils.encypt.DESPlus;
import com.naiterui.ehp.bp.utils.encypt.GengerCode;
import com.naiterui.ehp.bp.utils.encypt.MD5;
import com.naiterui.ehp.bp.utils.esign.ESignUtils;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;
import com.naiterui.ehp.bp.utils.lang.StringUtil;
import com.naiterui.ehp.bp.utils.sms.SMSUtil;
import com.naiterui.ehp.bs.pharmacist.entity.Pharmacist;
import com.naiterui.ehp.bs.pharmacist.entity.PharmacistLoginLog;
import com.naiterui.ehp.bs.pharmacist.exception.ExceptionCodes;
import com.naiterui.ehp.bs.pharmacist.feign.PharmacistEsignFeignClient;
import com.naiterui.ehp.bs.pharmacist.repository.mysql.PharmacistLoginLogRepository;
import com.naiterui.ehp.bs.pharmacist.repository.mysql.PharmacistRepository;
import com.naiterui.ehp.bs.pharmacist.service.ILoginService;
import com.naiterui.ehp.bs.pharmacist.service.ITencentDetectService;
import com.naiterui.ehp.bs.pharmacist.utils.Constants;
import com.naiterui.ehp.bs.pharmacist.utils.PhTencentCloudSdkUtil.FaceSyncVO;
import com.naiterui.ehp.bs.pharmacist.vo.login.AuthFlowParamVO;
import com.naiterui.ehp.bs.pharmacist.vo.login.AuthFlowVO;
import com.naiterui.ehp.bs.pharmacist.vo.login.AuthInfoStandardVO;
import com.naiterui.ehp.bs.pharmacist.vo.login.LoginVO;
import com.naiterui.ehp.bs.pharmacist.vo.user.AuthRegistCacheVO;
import com.naiterui.ehp.bs.pharmacist.vo.user.RegistVO;
import com.naiterui.ehp.bs.pharmacist.vo.user.RegisterParamVO;
import com.naiterui.ehp.bs.pharmacist.vo.user.TencentFaceIdVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.UUID;

import static com.naiterui.ehp.bs.pharmacist.utils.ChackPasswordUtils.validPwd;

/**
 * @Description 登陆服务
 * @Author gongbaoqiang
 * @Date 2019/12/2 11:48 AM
 */
@Slf4j
@Service
public class PharmacistLoginServiceImpl implements ILoginService {

    @Autowired
    private PharmacistRepository pharmacistRepository;
    @Autowired
    private ITencentDetectService tencentDetectService;
    @Autowired
    private PharmacistEsignFeignClient pharmacistEsignFeginClient;
    @Autowired
    private PharmacistLoginLogRepository pharmacistLoginLogRepository;

    public static final String HOSPITAL_NAME = ConfigUtil.getString("ehp.hospital.name");

    @Override
    public AuthFlowVO authFlow(AuthFlowParamVO flowParamVO) throws BusinessException {
        String authId = UUID.randomUUID().toString();

        // 1.校验注册短信验证码
        this.checkRegistCode(authId, flowParamVO.getPhoneNum(), flowParamVO.getVerifyCode());

        // 2.校验注册信息
        String password = this.checkRegistInfo(flowParamVO.getPhoneNum(), flowParamVO.getPassword(), flowParamVO.getSealImg());

        // 3.匹配药师用户记录(保存手写签名图片入库)
        Pharmacist pharmacist = this.matchingUserInfo(flowParamVO.getPhoneNum(), flowParamVO.getCardNo(), flowParamVO.getSealImg());

        // 4.初始化实名认证信息
        AuthInfoStandardVO authInfoStandardVO = AuthInfoStandardVO.builder()
                .phoneNum(pharmacist.getPhone())
                .name(pharmacist.getName())
                .cardNo(pharmacist.getCardNo())
                .cardImgs(Lists.newArrayList(pharmacist.getFrontUrl(), pharmacist.getBackUrl())).build();

        // 四川CA初始化人脸参数
        TencentFaceIdVO tencentAuthInfo = null;
        String serviceId = ESignUtils.getServiceId();
        if (EsignServiceProviderEnum.SCCA.getServiceId().equals(serviceId)) {
            pharmacistEsignFeginClient.checkPharmacistRealname(pharmacist.getId(), flowParamVO.getCardNo());
            tencentAuthInfo = tencentDetectService.getFaceId(pharmacist.getId());
        }

        // 5.设置实名认证流程缓存
        this.setAuthRegistCache(authId, pharmacist, password);

        // 删除注册短信验证码发送缓存
        RedisUtil.keyOps().delete(Constants.PHONE_REGIST_KEY + pharmacist.getPhone());
        return AuthFlowVO.builder().authId(authId).standardAuthInfo(authInfoStandardVO).tencentAuthInfo(tencentAuthInfo).build();
    }

    @Override
    public ResponseVO<RegistVO> authRegist(String authId) throws BusinessException {
        AuthRegistCacheVO authRegistCacheVO = Constants.getRdsPhAuthId(authId);
        if(authRegistCacheVO == null){
            log.warn("药师实名认证操作超时，请重新完成实名认证流程 authId:{}", authId);
            throw new BusinessException(ExceptionCodes.PHARMACIST_AUTH_ID_EXPIRE);
        }

        // 密码加密
        // 生成唯一的盐
        String salt;
        do {
            salt = GengerCode.generateSaltCode();
        } while (this.findExsitSalt(salt));

        Pharmacist pharmacist = this.pharmacistRepository.get(authRegistCacheVO.getId());
        // 保存药师注册信息
        try {
            pharmacist.setSalt(salt);
            pharmacist.setPassword(MD5.md5(authRegistCacheVO.getPassword() + salt));
            pharmacist.setLastPwdModify(new Date());
            pharmacist.setStatus(Pharmacist.STATUS_ACTIVE);
            pharmacist.setAuthStatus(Pharmacist.AUTH_STATUS_PASS);
            this.updateLogin(pharmacist);
        } catch (Exception e) {
            log.error("保存药师注册数据异常，phoneNum={}, msg={}", authRegistCacheVO.getPhoneNum(), e.getMessage());
            throw new BusinessException(ExceptionCodes.FAILED);
        }
        String resultSuccess = "成功";
        // 四川CA验证人脸核身结果
        String serviceId = ESignUtils.getServiceId();
        String cardNo = pharmacist.getCardNo();
        String pwd = cardNo.substring(cardNo.length() - 4).toUpperCase();
        if (EsignServiceProviderEnum.SCCA.getServiceId().equals(serviceId)) {
            this.getFaceSync(authId, pharmacist.getId(), cardNo);
            resultSuccess = String.format(ConfigUtil.getString("scca.apply.success"), pwd);
        } else if (EsignServiceProviderEnum.ESIGN_WUKONG_SDK.getServiceId().equals(serviceId)) {
            if (!RedisUtil.keyOps().existsKey(CommonConstant.REDIS_PRE_PHARMACIST_AUTHID_CHECK_CACHE_KEY + authId)) {
                throw new BusinessException(ExceptionCodes.PHARMACIST_AUTH_ID_EXPIRE);
            }
        } else {
            log.warn("药师注册电子签名厂商实名校验未实现");
        }
        // CA用户同步注册，弱失败，可联系管理员修改药师信息正确后重新注册认证
        pharmacistEsignFeginClient.esignPharmacistAdd(pharmacist.getId(), pharmacist.getName(), cardNo,
            pharmacist.getPhone(), HOSPITAL_NAME,
            StringUtils.defaultString(pharmacist.getSealImage(), pharmacist.getSealImage()),
            Pharmacist.AUTH_STATUS_PASS, pwd);
        if (EsignServiceProviderEnum.SCCA.getServiceId().equals(serviceId)) {
            // 缓存医生设置密码状态
            String statusKey = Constants.PHARMACIST_ESIGN_PWD_SET_KEY + pharmacist.getId();
            RedisUtil.valueOps().set(statusKey, true);
        }
        // 删除实名认证流程ID数据缓存
        Constants.delRdsPhAuthId(authId);
        RedisUtil.keyOps().delete(CommonConstant.REDIS_PRE_PHARMACIST_AUTHID_CHECK_CACHE_KEY + authId);
        return PublicService.returnResponseVO(ExceptionCodes.SUCCESS, resultSuccess,
            RegistVO.builder().authId(authId).pharmacistId(pharmacist.getId()).build());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public RegistVO saveLogin(RegisterParamVO paramVO) throws BusinessException {
        String phoneNum = paramVO.getPhoneNum();
        String pwd = paramVO.getPassword();
        String cardNo = paramVO.getCardNo();
        MultipartFile sealImg = paramVO.getSealImg();
        // 1. 验证手机是否存在
        Pharmacist pharmacist = this.findLoginByPhoneNum(phoneNum);
        if (pharmacist == null) {
            log.info("登陆信息为空: phoneNum={}", phoneNum);
            throw new BusinessException(ExceptionCodes.PHARMACIST_ACCOUNT_NOT_EXISTS);
        }

        //2. 验证身份证号是否一致，判断当前账户是否是本人操作
        if (!pharmacist.getCardNo().equals(cardNo)) {
            log.info("药师身份证号不匹配：cardNo={}", cardNo);
            throw new BusinessException(ExceptionCodes.PHARMACIST_ACTIVE_INFO_ERROE);
        }

        // 3.生成唯一的盐
        String salt = null;
        do {
            salt = GengerCode.generateSaltCode();
        } while (this.findExsitSalt(salt));

        String sealImagePath = null;
        // 生成签名印章
        if (sealImg != null) {
            String filepath =
                Constants.SEAL_PATH_PRE_PH + pharmacist.getId() + "_" + System.currentTimeMillis() + "_o.jpg";
            File signImgfile = new File(filepath);
            try {
                FileUtils.forceMkdirParent(signImgfile);
                FileUtils.copyInputStreamToFile(sealImg.getInputStream(), signImgfile);
            } catch (IOException e) {
                throw new BusinessException(ExceptionCodes.PHARMACIST_SEAL_IMG_UPLOAD_FAIL);
            }

            sealImagePath = filepath.replaceAll(Constants.FILE_BASE_PATH, "");
            pharmacist.setSealImage(sealImagePath);
            log.info("药师手写签名图片地址：path={}", sealImagePath);
        }

        // 4.保存药师注册信息
        try {
            pharmacist.setSalt(salt);
            pharmacist.setPassword(MD5.md5(pwd + salt));
            pharmacist.setLastPwdModify(new Date());
            pharmacist.setStatus(Pharmacist.STATUS_ACTIVE);
            this.updateLogin(pharmacist);

        } catch (Exception e) {
            log.error("保存药师注册数据异常，phoneNum={}, msg={}", phoneNum, e.getMessage());
            throw new BusinessException(ExceptionCodes.FAILED);
        }

        String authId = null;
        // 四川CA验证人脸核身结果
        String serviceId = ESignUtils.getServiceId();
        if (EsignServiceProviderEnum.SCCA.getServiceId().equals(serviceId)) {
            authId = UUID.randomUUID().toString();
            this.getFaceSync(authId, pharmacist.getId(), pharmacist.getCardNo());
        }

        // CA用户同步注册，弱失败，可联系管理员修改药师信息正确后重新注册认证
        pharmacistEsignFeginClient.esignPharmacistAdd(pharmacist.getId(), pharmacist.getName(), pharmacist.getCardNo(),
            pharmacist.getPhone(), HOSPITAL_NAME, StringUtils.defaultString(sealImagePath, pharmacist.getSealImage()),
            null, null);

        return RegistVO.builder().authId(authId).pharmacistId(pharmacist.getId()).build();
    }

    /**
     * <pre>
     *     判断密码盐是否已经存在
     * </pre>
     *
     * @param salt
     *
     * @return
     */
    private boolean findExsitSalt(String salt) {
        return pharmacistRepository.existsBySalt(salt);
    }

    @Override
    public LoginVO saveLoginInfoById(Long loginId, String loginKey, String password, String version, String platform, String deviceSN,
        String authId,
        HttpServletRequest request) throws BusinessException {
        // 1.根据药师loginId获取登录信息
        Pharmacist login = this.findById(loginId);
        if (login == null) {
            log.error("登陆获取医师数据异常,loginId={}", loginId);
            throw new BusinessException(ExceptionCodes.PHARMACIST_ACCOUNT_NOT_EXISTS);
        }

        // 激活认证
        if (!login.getStatus().equals(Pharmacist.STATUS_ACTIVE)) {
            throw new BusinessException(ExceptionCodes.PHARMACIST_NOT_ACTIVE);
        }

        String loginFailKey = Constants.getLoginFailKey(loginId);
        String loginFailValue = RedisUtil.valueOps().getString(loginFailKey);
        if (StringUtils.isNotBlank(loginFailValue) && Integer.valueOf(loginFailValue) >= Constants.LOGIN_FAIL_COUNT) {
            throw new BusinessException(ExceptionCodes.LOGIN_FAIL_EXCEED_COUNT);
        }
        // 2.验证登陆key
        try {
            String cacheLoginKey = Constants.PHARMACIST_LOGIN_KEY + String.valueOf(loginId);
            String cacheValue = RedisUtil.valueOps().getString(cacheLoginKey);
            if (cacheValue == null || !cacheValue.equals(loginKey)) {
                log.error("登陆验证登陆key错误，客户端加密key=" + loginKey + "后台缓存加密后为:" + cacheValue + ",loginId=" + loginId);
                throw new BusinessException(ExceptionCodes.LOGIN_FAIL_COUNT);
            }
        } catch (Exception e) {
            log.error("向redis中缓存获取用户登陆token失败,loginId={}", loginId, e);
            throw new BusinessException(ExceptionCodes.LOGIN_FAIL_COUNT, e);
        }

        password = MD5.md5(password.trim() + login.getSalt());
        if (!password.equals(login.getPassword())) {
            RedisUtil.keyOps().incr(loginFailKey);
            RedisUtil.keyOps().expire(loginFailKey, Constants.LOGIN_FAIL_LOCK_TIME);
            if (Integer.valueOf(RedisUtil.valueOps().getString(loginFailKey)) >= Constants.LOGIN_FAIL_COUNT) {
                throw new BusinessException(ExceptionCodes.LOGIN_FAIL_EXCEED_COUNT);
            }
            throw new BusinessException(ExceptionCodes.LOGIN_FAIL_COUNT);
        } else {
            RedisUtil.keyOps().delete(loginFailKey);
        }

        // 3.如果药师信息存在，则走登录流程进行登录
        return this.handleLogin(login, version, platform, deviceSN, authId, request);
    }

    @Override
    public LoginVO saveLoginInfoByPhone(String phoneNum, String version, String platform, String deviceSN,
        HttpServletRequest request)
        throws BusinessException {

        // 1.根据医师手机号获取登录信息
        Pharmacist login = this.findLoginByPhoneNum(phoneNum);

        // 2.医师不存在直接抛出异常
        if (login == null) {
            throw new BusinessException(ExceptionCodes.PHARMACIST_ACCOUNT_NOT_EXISTS);
        }

        // 3.如果信息存在，则走登录流程进行登录
        return this.handleLogin(login, version, platform, deviceSN, null, request);
    }

    @Override
    public LoginVO saveLoginCombine(Long loginId, String loginKey, String password, String phoneNum, String version, String platform, String deviceSN,
        HttpServletRequest request) throws BusinessException {

        // 1.根据药师loginId获取登录信息
        Pharmacist login = this.findLoginByPhoneNum(phoneNum);
        if (login == null) {
            log.error("登陆获取医师数据异常,loginId={}", loginId);
            throw new BusinessException(ExceptionCodes.PHARMACIST_ACCOUNT_NOT_EXISTS);
        }
        // 2.医师不存在直接抛出异常
        if (login == null) {
            throw new BusinessException(ExceptionCodes.PHARMACIST_ACCOUNT_NOT_EXISTS);
        }

        String loginFailKey = Constants.getLoginFailKey(loginId);
        String loginFailValue = RedisUtil.valueOps().getString(loginFailKey);
        if (StringUtils.isNotBlank(loginFailValue) && Integer.parseInt(loginFailValue) >= Constants.LOGIN_FAIL_COUNT) {
            throw new BusinessException(ExceptionCodes.LOGIN_FAIL_EXCEED_COUNT);
        }
        // 2.验证登陆key
        try {
            String cacheLoginKey = Constants.PHARMACIST_LOGIN_KEY + String.valueOf(loginId);
            String cacheValue = RedisUtil.valueOps().getString(cacheLoginKey);
            if (cacheValue == null || !cacheValue.equals(loginKey)) {
                log.error("登陆验证登陆key错误，客户端加密key=" + loginKey + "后台缓存加密后为:" + cacheValue + ",loginId=" + loginId);
                throw new BusinessException(ExceptionCodes.LOGIN_FAIL_COUNT);
            }
        } catch (Exception e) {
            log.error("向redis中缓存获取用户登陆token失败,loginId={}", loginId, e);
            throw new BusinessException(ExceptionCodes.LOGIN_FAIL_COUNT, e);
        }

        password = MD5.md5(password.trim() + login.getSalt());
        if (!password.equals(login.getPassword())) {
            RedisUtil.keyOps().incr(loginFailKey);
            RedisUtil.keyOps().expire(loginFailKey, Constants.LOGIN_FAIL_LOCK_TIME);
            if (Integer.parseInt(RedisUtil.valueOps().getString(loginFailKey)) >= Constants.LOGIN_FAIL_COUNT) {
                throw new BusinessException(ExceptionCodes.LOGIN_FAIL_EXCEED_COUNT);
            }
            throw new BusinessException(ExceptionCodes.LOGIN_FAIL_COUNT);
        } else {
            RedisUtil.keyOps().delete(loginFailKey);
        }

        // 3.如果信息存在，则走登录流程进行登录
        return this.handleLogin(login, version, platform, deviceSN, null, request);
    }

    @Override
    public Pharmacist findLoginByPhoneNum(String phoneNum) {

        return pharmacistRepository.findFirstByPhone(phoneNum);
    }

    @Override
    public void updateLogin(Pharmacist login) {
        pharmacistRepository.update(login);
    }

    // 处理登录逻辑，并返回用户相关信息
    private LoginVO handleLogin(Pharmacist login, String version, String platform, String deviceSN, String authId, HttpServletRequest request)
        throws BusinessException {

        if (login.getStatus() != null && login.getStatus() != Pharmacist.STATUS_ACTIVE) {
            throw new BusinessException(ExceptionCodes.PHARMACIST_ACCOUNT_NOT_NORMAL);
        }

        LoginVO loginVO = new LoginVO();

        boolean skipFaceAuth = false;
        String rdsAuthId = Constants.getRdsAuthId(login.getId());
        if (StringUtils.isNotBlank(authId) && StringUtils.isNotBlank(rdsAuthId)) {
            if (authId.equals(rdsAuthId)) {
                // 注册验证后自动登录
                skipFaceAuth = true;
            }
        }
        // 拦截已认证用户进行人脸实名验证
        if (!skipFaceAuth && ConfigUtil.getInt("user.login.realname.switch.ph") == 1) {
            if (Pharmacist.AUTH_STATUS_PASS.equals(login.getAuthStatus())) {
                // 查询实名认证结果，如未查询到，则提示实名
                FaceSyncVO faceSync = tencentDetectService.getFaceSync(login.getId());
                if (!faceSync.getResult()) {
                    log.warn("当前账户已完成备案，登录需进行实名认证 userId:{}", login.getId());
                    TencentFaceIdVO faceIdVO = tencentDetectService.getFaceId(login.getId());
                    loginVO.setTencentFaceAuth(faceIdVO);
                    throw new BusinessException(ExceptionCodes.LOGIN_NEED_VERIFY_AUTH, null, loginVO);
                }
            }
        }


        Long id = login.getId();
        loginVO.setPharmacistId(id);

        // 3.数据库回写token和最后登陆时间
        Date now = new Date();
        // 是否不互踢
        boolean loginKickEachOther = RedisUtil.keyOps().existsKey(Constants.PHARMACIST_LOGIN_KICK_EACH_OTHER);
        String token;
        // 不互踢
        if (loginKickEachOther) {
            token = DESPlus.gengerateLoginToken(login.getPhone());
        } else {
            token = DESPlus.gengerateLoginToken(login.getPhone() + UUID.randomUUID());
        }

        login.setToken(token);

        // 4.处理最后登录时间
        Integer firstFlag = LoginVO.FIRST_FLAG_NORMAL;

        if (Pharmacist.STATUS_NOT_ACTIVE.equals(login.getStatus())) {
            firstFlag = LoginVO.FIRST_FLAG_NEW_USER;
        }

        this.updateLogin(login);
        String model = request.getHeader(HeaderConstant.HEADER_MODEL);
        String ip = IpUtil.getIp(request);
        String loginAddr = IpUtil.getCityInfo(ip);
        this.saveLoginLog(login, model, ip, loginAddr, deviceSN);

        try {
            String cacheKey = Constants.PHARMACIST_LOGIN_TOKEN_KEY + id;
            RedisUtil.valueOps().set(cacheKey, token);
        } catch (Exception e) {
            log.error("向redis中缓存用户登陆token失败,pharmacistId={}", id, e);
            throw new BusinessException(ExceptionCodes.SMS_PHONE_VALID_FAIL, e);
        }

        loginVO.setToken(token);
        loginVO.setFirstFlag(firstFlag);


        return loginVO;
    }




    @Override
    public Pharmacist findById(Long id) {

        return pharmacistRepository.get(id);
    }



    @Override
    public void savePassword(Pharmacist login, String pwd) throws BusinessException {
        login.setPassword(MD5.md5(pwd + login.getSalt()));
        login.setChangedAt(new Date(System.currentTimeMillis()));
        login.setLastPwdModify(new Date());

        //        // 校验曾用密码
        //        Integer norepetTimes = ConfigUtil.getInt("dr.password.norepet.times");
        //
        //        if (norepetTimes != null && norepetTimes > 0) {
        //
        //            // 校验曾用密码
        //            boolean repet = beforePasswordService.verifyRepetBeforePassword(login.getId(), pwd, login.getSalt(), norepetTimes);
        //            if (repet) {
        //                log.warn("当前密码近期使用过，请重新设置密码 userId:{}", login.getId());
        //                throw new BusinessException(ExceptionCodes.PWD_NOREPET);
        //            }
        //            // 保存最近密码
        //            beforePasswordService.saveBeforePassword(login.getId(), pwd, login.getSalt());
        //        }
        //
        //        // 重新设置密码最后修改时间缓存
        //        PasswordExpireInterceptor.setDoctorLastPasswordModifyCache(doctorId, login.getLastPwdModify());
        this.updateLogin(login);
    }

    @Override
    public void saveLogoutLog(Pharmacist login, String model, String ip, String loginAddr, String deviceSN) {
        PharmacistLoginLog loginLog = PharmacistLoginLog.builder()
            .createdAt(new Date())
            .createdBy(login.getCreatedBy())
            .operate("退出登录")
            .deviceSN(deviceSN)
            .ip(ip)
            .loginAddr(loginAddr)
            .loginTime(new Date())
            .model(model)
            .name(login.getName())
            .phone(login.getPhone())
            .type(PharmacistLoginLog.TYPE_PHARMACIST)
            .userId(login.getId())
            .build();
        pharmacistLoginLogRepository.save(loginLog);
    }

    @Override
    public void saveLoginLog(Pharmacist login, String model, String ip, String loginAddr, String deviceSN) {
        PharmacistLoginLog loginLog = PharmacistLoginLog.builder()
            .createdAt(new Date())
            .createdBy(login.getCreatedBy())
            .operate("登录")
            .deviceSN(deviceSN)
            .ip(ip)
            .loginAddr(loginAddr)
            .loginTime(new Date())
            .model(model)
            .name(login.getName())
            .phone(login.getPhone())
            .type(PharmacistLoginLog.TYPE_PHARMACIST)
            .userId(login.getId())
            .build();
        pharmacistLoginLogRepository.save(loginLog);
    }

    /**
     * 算验证错误次数并判断redisKey对应的错误验证码验证次数是否在规定时间内超过规定值，
     * 如果超过验证次数，则返回true,否则返回false.
     *
     * @param cacheKey
     * @return
     */
    @Override
    public boolean validUserOperator(String cacheKey) {
        // 根据业务cacheKey，计算出验证validCacheKey和validCacheKey
        String validCacheCountKey = Constants.getSmsValidUserOperatorKey(cacheKey);
        String validCacheTimeKey = cacheKey + "|time";
        // 缓存中获取验证次数记录周期
        Object avlTimeObj = RedisUtil.hashOps().hget(Constants.VALID_KEY, Constants.VALID_TIME_FIELD);
        Integer avlTime = Integer.parseInt(avlTimeObj == null ? "0" : String.valueOf(avlTimeObj));
        if (avlTime == 0) {
            avlTime = Constants.DEFAULT_TIME_FIELD;
        }
        // validCacheTimeKey用于记录验证用户操作的周期
        // validCacheKey用于记录验证用户操作的次数
        if (!RedisUtil.keyOps().existsKey(validCacheTimeKey)) {
            RedisUtil.valueOps().set(validCacheTimeKey, "", avlTime);
            RedisUtil.keyOps().delete(validCacheCountKey);
        }
        // 缓存中获取验证次数
        long validCount = RedisUtil.keyOps().incr(validCacheCountKey);
        // 缓存中获取可操作次数
        Object avlCountObj = RedisUtil.hashOps().hget(Constants.VALID_KEY, Constants.VALID_COUNT_FIELD);
        Integer avlCount = Integer.parseInt(avlCountObj == null ? "0" : String.valueOf(avlCountObj));
        if (avlCount == 0) {
            avlCount = Constants.DEFAULT_COUNT_FIELD;
        }
        log.info("短信验证码使用验证key：" + cacheKey + "验证错误次数：" + validCount + ",最大可输入的验证错误次数：" + avlCount);
        return validCount > avlCount;
    }

    /**
     * 校验注册短信
     * @param authId
     * @param phoneNum
     * @param code
     */
    private void checkRegistCode(String authId, String phoneNum, String code) throws BusinessException {

        // 1.判断手机号的非空和格式
        if (phoneNum == null || "".equals(phoneNum)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_NULL);
        }
        if (!StringUtil.isMobileNum(phoneNum)) {
            throw new BusinessException(ExceptionCodes.SMS_PHONE_ERROR);
        }

        // 2.判断短信验证码的非空和正确性
        String redisKey = Constants.PHONE_REGIST_KEY + phoneNum;
        SMSUtil.validCode(code, redisKey, CommonConstant.REDIS_PRE_PHARMACIST);
    }

    /**
     * 校验注册信息，并返回解密后的密码
     * @param phoneNum
     * @param encodePassword
     * @param sealImg
     * @return
     * @throws BusinessException
     */
    private String checkRegistInfo(String phoneNum, String encodePassword, MultipartFile sealImg) throws BusinessException {
        String pwd = validPwd(phoneNum, encodePassword);

        if (ESignUtils.isHandWriting() && sealImg == null) {
            log.error("药师手写签名不能为空");
            throw new BusinessException(ExceptionCodes.SEAL_IMAGE_NOT_NULL);
        }
        RedisUtil.keyOps().delete(Constants.PHONE_REGIST_KEY + phoneNum);
        return pwd;
    }

    /**
     * 匹配药师记录
     * @param phoneNum
     * @param cardNo
     * @param sealImg
     * @return
     * @throws BusinessException
     */
    private Pharmacist matchingUserInfo(String phoneNum, String cardNo, MultipartFile sealImg) throws BusinessException {
        // 1. 验证手机是否存在
        Pharmacist pharmacist = this.findLoginByPhoneNum(phoneNum);
        if (pharmacist == null) {
            log.info("登录信息为空: phoneNum={}", phoneNum);
            throw new BusinessException(ExceptionCodes.PHARMACIST_ACCOUNT_NOT_EXISTS);
        }

        //2. 验证身份证号是否一致，判断当前账户是否是本人操作
        if (!pharmacist.getCardNo().equals(cardNo)) {
            log.info("药师身份证号不匹配：cardNo={}", cardNo);
            throw new BusinessException(ExceptionCodes.PHARMACIST_ACTIVE_INFO_ERROE);
        }

        String sealImagePath = null;
        // 生成签名印章
        if (sealImg != null) {
            String filepath = Constants.SEAL_PATH_PRE_PH + pharmacist.getId() + "_" + System.currentTimeMillis() + "_o.jpg";
            File signImgfile = new File(filepath);
            try {
                FileUtils.forceMkdirParent(signImgfile);
                FileUtils.copyInputStreamToFile(sealImg.getInputStream(), signImgfile);
            } catch (IOException e) {
                throw new BusinessException(ExceptionCodes.PHARMACIST_SEAL_IMG_UPLOAD_FAIL);
            }

            sealImagePath = filepath.replaceAll(Constants.FILE_BASE_PATH, "");
            pharmacist.setSealImage(sealImagePath);
            log.info("药师手写签名图片地址：path={}", sealImagePath);
        }

        // 4.保存药师注册信息
        try {
            this.updateLogin(pharmacist);
        } catch (Exception e) {
            log.error("保存药师注册数据异常，phoneNum={}, msg={}", phoneNum, e.getMessage());
            throw new BusinessException(ExceptionCodes.FAILED);
        }

        return pharmacist;
    }

    /**
     * 获取腾讯云人脸识别结果
     * @param authId
     * @param pharmacistId
     * @param cardNo
     * @return
     * @throws BusinessException
     */
    private FaceSyncVO getFaceSync(String authId, Long pharmacistId, String cardNo) throws BusinessException {
        FaceSyncVO faceSyncVO = this.tencentDetectService.getFaceSync(pharmacistId);
        if (!faceSyncVO.getResult()) {
            log.warn("药师注册信息核验-人脸识别未通过 pharmacistId:{}", pharmacistId);
            throw new BusinessException(ExceptionCodes.ILLEGAL_REQUEST, "注册申请需要先完成实名认证");
        }
        if (!faceSyncVO.getIdNo().equals(cardNo)) {
            log.warn("药师注册信息核验-实名身份证号与提交信息不匹配 pharmacistId:{}, reqIdNo:{}, authIdNo:{}", pharmacistId,
                    cardNo, faceSyncVO.getIdNo());
            throw new BusinessException(ExceptionCodes.ILLEGAL_REQUEST, "实名身份证号与提交信息不匹配");
        }

        // 设置注册验证认证ID
        Constants.setRdsAuthId(pharmacistId, authId);
        return faceSyncVO;
    }

    /**
     * 设置认证缓存
     * @param authId
     * @param pharmacist
     * @param password
     */
    private void setAuthRegistCache(String authId, Pharmacist pharmacist, String password){
        AuthRegistCacheVO authRegistCacheVO = AuthRegistCacheVO.builder()
                .authId(authId)
                .id(pharmacist.getId())
                .phoneNum(pharmacist.getPhone())
                .cardNo(pharmacist.getCardNo())
                .password(password)
                .build();
        Constants.setRdsPhAuthId(authId, authRegistCacheVO);
    }
}
